#include "nemu.h"
#include "common.h"
#include "../tlb/tlb.h"

uint32_t dram_read(hwaddr_t, size_t);
void dram_write(hwaddr_t, size_t, uint32_t);

/* Cache */
#define SET_NUM 128
#define SET_NUM_BITS 7

#define WAY_NUM 8
#define WAT_NUM_BITS 3

#define BLOCK_SIZE 64
#define BLOCK_SIZE_BITS 6

#define CACHE_SIZE 65536
#define CACHE_SIZE_BITS 16

typedef struct MyBlock {
	bool is_valid;
	uint32_t tag;
	char bytes[BLOCK_SIZE];
} MyBlock;

typedef struct MyCache {
	MyBlock blocks[SET_NUM][WAY_NUM];

	void (*init) (struct MyCache *this);
	uint32_t (*read) (struct MyCache *this, hwaddr_t addr, int len, bool *hit);
	void (*write) (struct MyCache *this, hwaddr_t addr, int len, uint32_t data, bool *hit);

} MyCache;

extern MyCache cache;

/* Memory accessing interfaces */

lnaddr_t seg_translate(swaddr_t addr, size_t len, uint8_t sreg) {
	if (cpu.cr0.protect_enable == 0) {
		return addr;
	} else {
		return cpu.sreg[sreg].base + addr;
	}
}

hwaddr_t page_translate(lnaddr_t addr) {
	if (cpu.cr0.protect_enable == 1 && cpu.cr0.paging == 1) {
		PDE pde;
		PTE pte;
		bool flag;
		hwaddr_t hwaddr;
		uint32_t dir, page, offset;

		dir = (addr >> 22) & 0x3ff;
		page = (addr >> 12) & 0x3ff;
		offset = addr & 0xfff;

		hwaddr = tlb.read(&tlb, addr, &flag);
		if (flag) {
			//Log("Hit: lnaddr: 0x%08x, hwaddr: 0x%08x\n", addr, hwaddr);
			//tlb.disp(&tlb);
			hwaddr = (hwaddr << 12) + offset;
			return hwaddr;
		}

		hwaddr_t dir_base, dir_pos;
		dir_base = cpu.cr3.page_directory_base;
		dir_pos = (dir_base << 12) + (dir << 2);
		pde.val = hwaddr_read(dir_pos, 4);
		Assert(pde.present == 1, "Unable to access dir!");

		hwaddr_t page_base, page_pos;
		page_base = pde.page_frame;
		page_pos = (page_base << 12) + (page << 2);
		pte.val = hwaddr_read(page_pos, 4);
		Assert(pte.present == 1, "Unable to access page!");

		tlb.write(&tlb, addr, pte.page_frame);

		hwaddr = (pte.page_frame << 12) + offset;
		//Log("Miss: lnaddr: 0x%08x, hwaddr: 0x%08x\n", addr, hwaddr);
		return hwaddr;
	} else {
		return addr;
	}
}

uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
	bool hit;
	return cache.read(&cache, addr, len, &hit);
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
	bool hit;
	cache.write(&cache, addr, len, data, &hit);
}

uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
	hwaddr_t hwaddr;
	uint32_t offset;
	offset = addr & 0xfff;
	if (offset + len <= PAGE_SIZE) {
		hwaddr = page_translate(addr);
		return hwaddr_read(hwaddr, len);
	} else {
		//Assert(0, "Don't support read or write data cross page yet.");
		int l;
		uint32_t data;
		l = PAGE_SIZE - offset;

		hwaddr = page_translate(addr);
		data = hwaddr_read(hwaddr, l);

		addr += l;
		hwaddr = page_translate(addr);
		data = (data << ((len - l) << 3)) + hwaddr_read(page_translate(hwaddr), len - l);
		return data;
	}
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
	hwaddr_t hwaddr;
	uint32_t offset;
	offset = addr & 0xfff;
	if (offset + len <= PAGE_SIZE) {
		hwaddr = page_translate(addr);
		hwaddr_write(hwaddr, len, data);
	} else {
		//Assert(0, "Don't support read or write data cross page yet.");
		int l;
		l = PAGE_SIZE - offset;

		hwaddr = page_translate(addr);
		hwaddr_write(hwaddr, l, data >> ((len - l) << 3));

		addr += l;
		hwaddr = page_translate(addr);
		hwaddr_write(hwaddr, len - l, data);
	}
}

uint32_t swaddr_read(swaddr_t addr, size_t len, uint8_t sreg) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr = seg_translate(addr, len, sreg);
	//Log("swaddr: 0x%08x, lnaddr: 0x%08x\n", addr, lnaddr);
	return lnaddr_read(lnaddr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data, uint8_t sreg) {
#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
#endif
	lnaddr_t lnaddr = seg_translate(addr, len, sreg);
	//Log("swaddr: 0x%08x, lnaddr: 0x%08x\n", addr, lnaddr);
	lnaddr_write(lnaddr, len, data);
}

